#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <strings.h>

//#define TIME_GC

#ifdef TIME_GC
#include <sys/time.h>
#endif

#include "gc.h"
#include "objects.h"
#include "vm.h"
#include "glovars.h"
#include "interpreter.h"
//#include "bytecode.h"


typedef struct list {
  scm *data;
  struct list *next;
  struct list *prev;
} list;

struct list *gc_objects = NULL;

scm gc_timer = 0;
scm gc_fib = 1;
scm gc_timeout = 1;

void gc_list_push(scm *p) {
  struct list *link;

  link = malloc(sizeof(list));

  link->data = p;
  link->next = gc_objects;
  if(link->next) {
    link->next->prev = link;
  }
  link->prev = NULL;
  
  gc_objects = link;
}

#ifdef TIME_GC
FILE *fptr;
#endif

scm *heap_alloc(scm n) {
  scm *p;
  scm tmp;

  gc_timer++;
  if( (gc_timer > gc_timeout)) {
    gc_timer = 0;
    
    tmp = gc_timeout;
    gc_timeout += gc_fib;
    gc_fib = tmp;

#ifdef TIME_GC
    if(!fptr) {
	    fptr = fopen("gc-log.txt", "w");
    }
    struct timeval time;
    long microsec;
    gettimeofday(&time, NULL);
    microsec = ((unsigned long long)time.tv_sec * 1000000) + time.tv_usec;
    fprintf(fptr, "%ld 0\n", microsec);
#endif
    
    mark();
    sweep();

#ifdef TIME_GC
    gettimeofday(&time, NULL);
    microsec = ((unsigned long long)time.tv_sec * 1000000) + time.tv_usec;
    fprintf(fptr, "%ld 1\n", microsec);
#endif
  }

  p = calloc(n, sizeof(scm));

  gc_list_push(p);

  return p;
}

/*
scm heap_alloc_port(FILE *a, FILE *b) {
	scm *p;

	p = heap_alloc(3);
	
	p[0] = make_hdr(HDR_WHITE, 2, 0);
	p[1] = SCM_PTR(a);
	p[2] = SCM_PTR(b);

	return mk_port(p);
}
*/

//#define DEBUG

void mark() {

//  int i;

  scm gc_stack_ptr;
  scm gc_stack_base_ptr;

  scm tmp;

  struct global *g;

#ifdef DEBUG
  puts("MARK REG");
#endif
  
  mark_object(reg_acc);
  mark_object(reg_clo);
  if (reg_env)
	  mark_object(scm_puttag(reg_env-2, TAG_CLOS));

#ifdef DEBUG
  puts("MARK GLO");
#endif
  
  if(glovars) {
  for(g = glovars; g; g = g->next) {
    mark_object(g->val);
  }
  }

#ifdef DEBUG
  puts("MARK STK");
#endif
  
  gc_stack_ptr = reg_rsp;
  gc_stack_base_ptr = reg_rbp;

  while(gc_stack_ptr > gc_stack_base_ptr) {
	  mark_object(stack[gc_stack_ptr--]);
  }
  
  while(gc_stack_ptr > 0) {
	  tmp = stack[gc_stack_ptr--];
	  assert(tmp == 0xDEADBEEFDEADBEEF);
	  
	  // rbp
	  gc_stack_base_ptr = stack[gc_stack_ptr--];
	  
	  // reg env
	  if(stack[gc_stack_ptr])
		  mark_object(scm_puttag(((scm*)PTR_SCM(stack[gc_stack_ptr]))-2, TAG_CLOS));
	  gc_stack_ptr--;
	  
	  // ret addr
	  gc_stack_ptr--;
	  
	  tmp = stack[gc_stack_ptr--];
	  assert(tmp == 0xC0FFEEEEEEEEEEEE);
	  
#ifdef DEBUG
	  puts("     FRAME");
#endif
	  
	  while(gc_stack_ptr > gc_stack_base_ptr) {
		  mark_object(stack[gc_stack_ptr--]);
	  }
  }
  
#ifdef DEBUG
  puts("MARK ARGS");
#endif

/*
  for(i = 0; i < bytecode_args_num; i++) {
    mark_object(bytecode_args[i]);
  }
*/
  
#ifdef DEBUG
  puts("MARK.");
#endif

}

void mark_object(scm obj) {
  scm *p;
  scm hdr;

  scm raw_size, scm_size;

  int i;
  if (scm_isptr(obj)) {
    p = (scm*)(obj & ~0b111);
    hdr = *p;

    if(get_hdr_color(hdr) == HDR_BLACK)
	    return;
    
    *p = set_hdr_color(hdr, HDR_BLACK);
    
    raw_size = get_hdr_raw_size(hdr);
    scm_size = get_hdr_scm_size(hdr);
    
    for(i = 0; i < scm_size; i++) {
      mark_object(p[1 + raw_size + i]);
    }
  }
}

void sweep() {
  struct list *link;
  struct list *tmp;
  scm hdr;

  link = gc_objects;
  while(link) {
    //printf("%p\n", link->data);
    hdr = *(link->data);

    if(get_hdr_color(hdr) == HDR_WHITE) {
      bzero(link->data, (1 + get_hdr_raw_size(hdr) + get_hdr_scm_size(hdr)) * sizeof(scm));
      free(link->data);

      tmp = link;
      link = link->next;

      if(tmp->prev)
	tmp->prev->next = tmp->next;
      else {
	gc_objects = tmp->next;
	tmp->next->prev = NULL;
      }

      if(tmp->next)
	tmp->next->prev = tmp->prev;

      tmp->data = NULL;
      tmp->next = NULL;
      tmp->prev = NULL;
      free(tmp);
      
      /*
      bzero(link->data, (1 + get_hdr_raw_size(hdr) + get_hdr_scm_size(hdr)) * sizeof(scm));
      free(link->data);

      if(link->prev) {
	link->prev->next = link->next;
      }
      else {
	gc_objects = link;
      }
      
      if(link->next) {
	link->next->prev = link->prev;
      }
      
      tmp = link;
      link = link->next;

      tmp->data = NULL;
      tmp->next = NULL;
      tmp->prev = NULL;
      free(tmp);
      */
    }
    else {
      *(link->data) = set_hdr_color(hdr, HDR_WHITE);
      
      link = link->next;
    }
  }
}
